﻿#if UNITY_2019_1_OR_NEWER
#define UNITY_3D
using UnityEngine;
#endif
using System.Collections;
using System.Collections.Generic;

namespace Devil
{
    using Handler = ParallelDispatcher.Handler;
    using CombinedHandler = ParallelDispatcher.CombinedHandler;

    public interface ICommandScheduler
    {
        Handler Schedule(Handler dependency);
    }

    /// <summary>
    /// 异步指令执行缓存，（用以预处理指令执行顺序，然后合批执行）
    /// </summary>
    public class ParallelCommandBuffer : ICommandHandler, ICommandScheduler, IExecutableProgress
    {

        abstract class CommandScheduler
        {
            internal readonly bool mainThread;
            internal readonly bool append;
            internal CommandScheduler(bool mainThread, bool append)
            {
                this.mainThread = mainThread;
                this.append = append;
            }
            internal abstract Handler Schedule(Handler dependency);
        }

        class ScheduleGroup: CommandScheduler
        {
            ICommandScheduler scheduler;
            internal ScheduleGroup(ICommandScheduler scheduler, bool append) : base(false, append)
            {
                this.scheduler = scheduler;
            }

            internal override Handler Schedule(Handler dependency)
            {
                return scheduler.Schedule(dependency);
            }
        }

        class ScheduleHandler : CommandScheduler
        {
            Handler mHandler;
            internal ScheduleHandler(Handler handler, bool append) : base(false, append)
            {
                mHandler = handler;
            }

            internal override Handler Schedule(Handler dependency)
            {
                return ParallelUtils.Combine(dependency, mHandler);
            }
        }

        class ScheduleICommand : CommandScheduler
        {
            ICommand cmd;
            internal ScheduleICommand(ICommand cmd, bool mainThread, bool append) : base(mainThread, append)
            {
                this.cmd = cmd;
            }

            internal override Handler Schedule(Handler dependency)
            {
                return cmd.Schedule(mainThread, dependency);
            }
        }

        class ScheduleIYieldCmd: CommandScheduler
        {
            IYieldCommand cmd;
            internal ScheduleIYieldCmd(IYieldCommand cmd, bool mainThread, bool append) : base(mainThread, append)
            {
                this.cmd = cmd;
            }
            internal override Handler Schedule(Handler dependency)
            {
                return cmd.Schedule(mainThread, dependency);
            }
        }

        class ScheduleIParallelFor<T> : CommandScheduler
        {
            ICommandParallelFor<T> cmd;
            IEnumerable iter;
            internal ScheduleIParallelFor(ICommandParallelFor<T> cmd, bool append, IEnumerable iter) : base(false, append)
            {
                this.cmd = cmd;
                this.iter = iter;
            }

            internal override Handler Schedule(Handler dependency)
            {
                return cmd.Schedule<T>(iter, dependency);
            }
        }

        class ScheduleIParallelForCount : CommandScheduler
        {
            ICommandParallelFor<int> cmd;
            int count;
            internal ScheduleIParallelForCount(ICommandParallelFor<int> cmd, bool append, int count) : base(false, append)
            {
                this.cmd = cmd;
                this.count = count;
            }

            internal override Handler Schedule(Handler dependency)
            {
                return cmd.Schedule(count, dependency);
            }
        }

        long mQueryId;
        float mProgress;

        readonly List<CommandScheduler> mCommands;
        Handler mHandler;
#if UNITY_EDITOR
        bool mSampleExecutTime;
        string mSampleName;
#endif

        string mName;
        public string Name
        {
            get { return mName; }
            set
            {
                mName = value;
            }
        }

        public float Progress => QueryProgress();
        public bool IsComplete => !mHandler.IsAlive && mCommands.Count == 0;
        // 已经完成规划
        public bool IsScheduled => mCommands.Count == 0;

        public ParallelCommandBuffer()
        {
            mCommands = new List<CommandScheduler>();
        }

        public ParallelCommandBuffer(string name)
        {
            mName = name;
            mCommands = new List<CommandScheduler>();
        }

        public Handler GetHandler()
        {
            return mHandler;
        }

        [System.Obsolete("This method will be instead of \"SampleExecuteTime\"", false)]
        public void SampleExecutingTime(string sampleName)
        {
            SampleExecuteTime(sampleName);
        }

        public void SampleExecuteTime(string sampleName = null)
        {
#if UNITY_EDITOR
            mSampleName = sampleName;
            mSampleExecutTime = true;
#endif
        }

        public void AddCommand(Handler handler, bool appendMode)
        {
            if (!handler.IsAlive && !appendMode)
                return;
            var info = new ScheduleHandler(handler, appendMode);
            lock (mCommands)
            {
                mCommands.Add(info);
            }
        }

        public void AddCommand(ICommand cmd, bool mainThread, bool appendMode)
        {
            if (cmd == null)
                return;
            var info = new ScheduleICommand(cmd, mainThread, appendMode);
            lock (mCommands)
            {
                mCommands.Add(info);
            }
        }

        public void AddCommand(IYieldCommand cmd, bool mainThread, bool appendMode)
        {
            if (cmd == null)
                return;
            var info = new ScheduleIYieldCmd(cmd, mainThread, appendMode);
            lock (mCommands)
            {
                mCommands.Add(info);
            }
        }

        public void AddCommand<T>(ICommandParallelFor<T> cmd, IEnumerable enumerable, bool appendMode)
        {
            if (cmd == null)
                return;
            var info = new ScheduleIParallelFor<T>(cmd, appendMode, enumerable);
            lock (mCommands)
            {
                mCommands.Add(info);
            }
        }

        public void AddCommand(ICommandParallelFor<int> cmd, int count, bool appendMode)
        {
            if (cmd == null)
                return;
            var info = new ScheduleIParallelForCount(cmd, appendMode, count);
            lock(mCommands)
            {
                mCommands.Add(info);
            }
        }

        public void AddCommandScheduler(ICommandScheduler scheduler, bool appendMode)
        {
            if (scheduler == null)
                return;
            var info = new ScheduleGroup(scheduler, appendMode);
            lock(mCommands)
            {
                mCommands.Add(info);
            }
        }

        public Handler Schedule(Handler dependency)
        {
            lock (mCommands)
            {
                int index = 0;
                int end = index;
                Handler prev = ParallelUtils.Combine(dependency, mHandler);
                var combineName = string.IsNullOrEmpty(mName) ? "parallel command buffer" : $"pcb<{mName}>";
                while (index < mCommands.Count)
                {
                    end = index + 1;
                    while(end < mCommands.Count)
                    {
                        if (mCommands[end].append)
                            break;
                        end++;
                    }
                    if(end > index + 1)
                    {
                        CombinedHandler combine = ParallelUtils.NewCombination(combineName);
                        for (int i = index; i < end; i++)
                        {
                            var cmd = mCommands[i];
                            var handle = cmd.Schedule(prev);
#if UNITY_EDITOR
                            if (mSampleExecutTime)
                                handle.SampleExecuteTime(mSampleName);
#endif
                            combine.Combine(handle);
                        }
                        prev = combine.GetHandler();
                    }
                    else
                    {
                        prev = mCommands[index].Schedule(prev);
#if UNITY_EDITOR
                        if (mSampleExecutTime)
                            prev.SampleExecuteTime(mSampleName);
#endif
                    }
                    index = end;
                }
                mHandler = prev;
                mCommands.Clear();
            }
            return mHandler;
        }

        public void Clear()
        {
            lock (mCommands)
            {
                mCommands.Clear();
            }
        }

        public float QueryProgress()
        {
            long queryId;
            bool validate;
#if UNITY_3D
            queryId = ParallelUnity.frameCount;
            validate = queryId != mQueryId;

            queryId = System.DateTime.UtcNow.Ticks;
            validate = queryId >= mQueryId + ParallelDispatcher.TICK_MILLIS * 10000;
#else
#endif
            if(validate)
            {
                mQueryId = queryId;
                //calculate progress
                var exec = mHandler.GetExecution();
                mProgress = exec.progress;
            }
            return mProgress;
        }
    }
}
